﻿//
// PhysicsInterface.cs
// 
// This file is part of the QuickStart Game Engine.  See http://www.codeplex.com/QuickStartEngine
// for license details.
//

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace Alex_s_Dream_The_Toy_World
{
    /// <summary>
    /// All camera messages will be handled by the camera interface. It will allow
    /// anyone in the code that has access to this interface to request camera information.
    /// </summary>
    public class PhysicsInterface : Interface
    {
        /// <summary>
        /// Gets the <see cref="IPhysicsScene"/> reference for this scene.
        /// </summary>
        public IPhysicsScene PhysicsScene
        {
            get { return this.physicsScene; }
        }
        private IPhysicsScene physicsScene;

        /// <summary>
        /// A list of all the physics actors in the overall scene
        /// </summary>
        private readonly Dictionary<Int64, IPhysicsActor> actors;

        /// <summary>
        /// Create a <see cref="PhysicsInterface"/>.
        /// </summary>
        public PhysicsInterface(FrameworkCore game, InterfaceType interfaceType)
            : base(game, interfaceType)
        {
            this.game.GameMessage += this.Game_GameMessage;

            this.actors = new Dictionary<Int64, IPhysicsActor>();

            this.physicsScene = game.Physics.CreateScene();
        }

        public override void Shutdown()
        {
            foreach (IPhysicsActor actor in this.actors.Values)
            {
                actor.Dispose();
            }

            actors.Clear();

            if (this.physicsScene != null)
            {
                this.physicsScene.KillProcessingThread();
                this.physicsScene.Dispose();
                this.physicsScene = null;
            }
        }

        /// <summary>
        /// Adds a <see cref="IPhysicsActor"/> to the overall scene.
        /// </summary>
        /// <param name="inActor">Actor to be placed in the scene.</param>
        public void AddActor(Int64 EntityUniqueID, IPhysicsActor inActor)
        {
            this.actors.Add(EntityUniqueID, inActor);
        }

        /// <summary>
        /// Removes an actor from the overall scene.
        /// </summary>
        /// <param name="targetActor">Actor to be removed.</param>
        public void RemoveActor(Int64 EntityUniqueID, IPhysicsActor targetActor)
        {
            this.actors.Remove(EntityUniqueID);
        }

        /// <summary>
        /// Message listener for messages that are not directed at any particular Entity or Interface.
        /// </summary>
        /// <param name="message">Incoming message</param>
        /// <exception cref="ArgumentException">Thrown if a <see cref="MessageType"/> is not handled properly."/></exception>
        protected virtual void Game_GameMessage(IMessage message)
        {
            ExecuteMessage(message);
        }

        /// <summary>
        /// Message handler for all incoming messages.
        /// </summary>
        /// <param name="message">Incoming message</param>
        /// <exception cref="ArgumentException">Thrown if a <see cref="MessageType"/> is not handled properly."/></exception>
        public override bool ExecuteMessage(IMessage message)
        {
            switch (message.Type)
            {
                case MessageType.GetPhysicsScene:
                    {
                        MsgGetPhysicsScene getPhysSceneMsg = message as MsgGetPhysicsScene;
                        message.TypeCheck(getPhysSceneMsg);

                        getPhysSceneMsg.PhysicsScene = this.physicsScene;
                    }
                    return true;
                case MessageType.AddEntityToPhysicsScene:
                    {
                        MsgAddEntityToPhysicsScene msgAddEntity = message as MsgAddEntityToPhysicsScene;
                        message.TypeCheck(msgAddEntity);

                        if (msgAddEntity.EntityID != FrameworkCore.UniqueIDEmpty
                            && msgAddEntity.Actor != null)
                        {
                            this.actors.Add(msgAddEntity.EntityID, msgAddEntity.Actor);
                        }
                    }
                    return true;
                case MessageType.RemoveEntityFromPhysicsScene:
                    {
                        MsgRemoveEntityFromPhysicsScene remPhysMsg = message as MsgRemoveEntityFromPhysicsScene;
                        message.TypeCheck(remPhysMsg);

                        if (remPhysMsg.EntityID != FrameworkCore.UniqueIDEmpty)
                        {
                            IPhysicsActor actor;
                            if (this.actors.TryGetValue(remPhysMsg.EntityID, out actor))
                            {
                                this.actors.Remove(remPhysMsg.EntityID);
                                this.physicsScene.ScheduleForDeletion(actor);
                            }
                        }
                    }
                    return true;
                case MessageType.BeginPhysicsFrame:
                    {
                        MsgBeginPhysicsFrame msgBeginFrame = message as MsgBeginPhysicsFrame;
                        message.TypeCheck(msgBeginFrame);

                        // Begin next physics frame.
                        this.physicsScene.BeginFrame(msgBeginFrame.GameTime);
                    }
                    return true;
                case MessageType.EndPhysicsFrame:
                    {
                        // Wait for previous physics frame to finish.
                        this.physicsScene.EndFrame();
                    }
                    return true;
                case MessageType.SetGravity:
                    {
                        MsgSetGravity msgSetGrav = message as MsgSetGravity;
                        message.TypeCheck(msgSetGrav);

                        this.physicsScene.Gravity = msgSetGrav.Gravity;
                    }
                    return true;
                case MessageType.GetGravity:
                    {
                        MsgGetGravity msgGetGrav = message as MsgGetGravity;
                        message.TypeCheck(msgGetGrav);

                        msgGetGrav.Gravity = this.physicsScene.Gravity;
                    }
                    return true;
                case MessageType.SetPhysicsTimeStep:
                    {
                        MsgSetPhysicsTimeStep msgSetPhysStep = message as MsgSetPhysicsTimeStep;
                        message.TypeCheck(msgSetPhysStep);

                        this.physicsScene.SetPhysicsTimeStep(msgSetPhysStep.TimeStep);
                    }
                    return true;
                default:
                    return false;
            }
        }
    }
}
